package com.zhuhjay.system.aspectj;

import com.zhuhjay.common.annotation.ApiLimit;
import com.zhuhjay.common.exception.CustomException;
import com.zhuhjay.common.utils.ClientUtils;
import com.zhuhjay.common.utils.ServletUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * Api访问限制的切面 AOP
 * @author ZhuHJay
 * @date 2022/3/30 19:07
 */
@Slf4j
@Aspect
@Component
@Order(1)
public class ApiLimitAspect {

    private final RedisTemplate<String, Object> redisTemplate;

    public ApiLimitAspect(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /** 配置切入点 **/
    @Pointcut("@annotation(com.zhuhjay.common.annotation.ApiLimit)")
    public void pointCut() {}

    /** 前置通知 **/
    @Before("pointCut()")
    public void before(JoinPoint joinPoint){
        // 从切面织入点处通过反射机制获取织入点处的方法
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        ApiLimit apiLimitAnnotation = method.getAnnotation(ApiLimit.class);
        int limitCount = apiLimitAnnotation.count();
        // 拼接key -> USER_IP:127.0.0.1:URI:/system/user/login
        String redisKey = "USER_IP:" +
                ClientUtils.getIpAddress(ServletUtils.getRequest()) +
                ":URI:" + ServletUtils.getRequest().getRequestURI();
        Integer redisValue = (Integer) redisTemplate.opsForValue().get(redisKey);
        // 判断是否存在
        if( redisValue != null ){
            // 判断访问次数是否超标
            if( ++redisValue > limitCount ) {
                log.error("用户[{}]使用IP[{}]访问API[{}]上限",
                        ServletUtils.getParameter("username"),
                        ClientUtils.getIpAddress(ServletUtils.getRequest()),
                        ServletUtils.getRequest().getRequestURI());
                throw new CustomException("接口访问过于频繁, 请稍后重试");
            }else{
                // 没有就更新次数
                // 第三个参数表示偏移量, 不加的话过期时间就没了
                redisTemplate.opsForValue().set(redisKey, redisValue, 0);
            }
        }else{
            // 不存在就创建, 设置超时时间为一分钟
            redisTemplate.opsForValue().set(redisKey, 1, 1, TimeUnit.MINUTES);
        }
    }

}
